Skip to content

fix(respect): record request bodies in the HAR postData entry - #2992

Open
ariesclark wants to merge 1 commit into
Redocly:mainfrom
ariesclark:fix/respect-har-post-data
Open

fix(respect): record request bodies in the HAR postData entry#2992
ariesclark wants to merge 1 commit into
Redocly:mainfrom
ariesclark:fix/respect-har-post-data

Conversation

@ariesclark

@ariesclark ariesclark commented Jul 30, 2026

Copy link
Copy Markdown

What/Why/How?

withHar recorded postData: {} for every request, so a HAR from respect --har-output never contained the request body. Replaying that capture through drift skips request-body validation and reports no problems. Missing data reads as a pass.

buildPostData serializes the body and reads the mime type from the request's content-type, going through buildHeaders so it treats every header shape alike. It records only text bodies, omitting FormData, streams, and binary rather than stringifying them to [object Object]. request.bodySize now carries the byte length instead of -1.

Reference

None.

Testing

build-post-data.test.ts covers the content-type sources, the application/octet-stream fallback, and the cases that must record nothing: no body, empty string, Buffer, FormData, plain object.

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

The HAR now stores request bodies verbatim, so capturing a login flow puts credentials in the file. The capture already recorded headers and response bodies unmasked, so this widens an existing exposure rather than adding a new kind.


Note

Medium Risk
HAR files now store request bodies verbatim (including credentials in login flows), widening an existing capture exposure; behavior change is localized to respect HAR logging with conservative omission for non-text bodies.

Overview
Fixes HAR capture for respect --har-output so request bodies are no longer always recorded as empty postData: {}, which caused drift replay to skip request-body validation and look successful when nothing was checked.

A new buildPostData helper builds HAR postData from options.body and content-type (via the same buildHeaders paths as the rest of the HAR). It records string and URLSearchParams bodies, uses application/octet-stream when no content type is set, and omits bodies it cannot safely serialize (no body, empty string, Buffer, FormData, plain objects).

withHar now sets request.postData from that helper and request.bodySize to the UTF-8 byte length when text is present (otherwise -1).

Reviewed by Cursor Bugbot for commit 8c65702. Bugbot is set up for automated code reviews on this repo. Configure here.

@ariesclark
ariesclark requested review from a team as code owners July 30, 2026 16:20
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8c65702

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@redocly/cli Patch
@redocly/openapi-core Patch
@redocly/respect-core Patch
@redocly/client-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@DmitryAnansky

DmitryAnansky commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@ariesclark
Could you please clarify what you mean by “never contained the request body”?
Below is an example of a HAR log entry for a POST request. It contains both the request (including the request body) and the response.

If I understand correctly, the main issue is that the postData field is missing. Is that right?

{
        "_compressed": false,
        "_resourceType": "fetch",
        "_timestamps": {
          "start": [
            310944,
            99464625
          ],
....
        },
        "timings": {
 ....
        },
        "time": 324.604041,
        "startedDateTime": "2026-07-31T08:53:25.073Z",
        "cache": {
          "beforeRequest": null,
          "afterRequest": null
        },
        "request": {
          "method": "POST",
          "url": "https://redocly.com/_mock/demo/openapi/museum-api/tickets",
          "cookies": [],
          "headers": [
            {
              "name": "content-type",
              "value": "application/json"
            },
            {
              "name": "accept",
              "value": "application/json, application/problem+json"
            },
            {
              "name": "authorization",
              "value": "Basic Og=="
            }
          ],
          "queryString": [],
          "headersSize": -1,
          "bodySize": -1,
          "postData": {},
          "httpVersion": "HTTP/1.1"
        },
        "response": {
          "headers": [
            {
              "name": "access-control-allow-headers",
              "value": "*"
            },
            .....
          ],
          "cookies": [],
          "status": 201,
          "statusText": "Created",
          "httpVersion": "HTTP/1.1",
          "redirectURL": "",
          "content": {
            "size": 199,
            "mimeType": "application/json",
            "text": "{\"message\":\"Museum general entry ticket purchased\",\"ticketId\":\"382c0820-0530-4f4b-99af-13811ad0f17a\",\"ticketType\":\"general\",\"ticketDate\":\"2023-09-07\",\"confirmationCode\":\"ticket-general-e5e5c6-dce78\"}",
            "compression": 0
          },
          "bodySize": 199,
          "headersSize": 3038
        },
        "pageref": "page_1"
      }

@DmitryAnansky

Copy link
Copy Markdown
Contributor

From the description:
...."so capturing a login flow puts credentials in the file"...

Actually, the HAR is passed through conditionallyMaskSecrets before being written

)?.value;

return {
mimeType: typeof contentType === 'string' ? contentType : 'application/octet-stream',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetch sets this content type itself when a URLSearchParams body is sent without an explicit header:

const fallbackMimeType =
    body instanceof URLSearchParams
      ? 'application/x-www-form-urlencoded;charset=UTF-8'
      : 'application/octet-stream';

  return {
    mimeType: typeof contentType === 'string' ? contentType : fallbackMimeType,
    text,
  };

headersSize: -1,
bodySize: -1,
postData: {},
bodySize: postData.text === undefined ? -1 : Buffer.byteLength(postData.text),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bodySize: postData.text === undefined ? -1 : Buffer.byteLength(postData.text),
// -1 means "not available": a body was sent but not recorded (for example FormData).
bodySize:
postData.text !== undefined ? Buffer.byteLength(postData.text) : options.body ? -1 : 0,
...(postData.text !== undefined && { postData }),

@ariesclark

Copy link
Copy Markdown
Author

@ariesclark Could you please clarify what you mean by “never contained the request body”? Below is an example of a HAR log entry for a POST request. It contains both the request (including the request body) and the response.

If I understand correctly, the main issue is that the postData field is missing. Is that right?

Yup, that's right, postData is the only thing missing. My follow up PR for the coverage command depends on knowing each bodies weren't sent for testing, and depends on this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants